package com.packenius.library.xspdf;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

/**
 * A "column" is part of a page and contains text and/or images. Every page can contain one or more columns (no limit).
 * @author Christian Packenius, 2013.
 */
public class XSColumn {
  final XSPage page;

  /**
   * All text lines within this column, ordered from yStart to bottom and from xStart to right (if neccessary).
   */
  final List<XSTextLine> textLines = new ArrayList<XSTextLine>();

  /**
   * Current column text line.
   */
  XSTextLine currentTextLine;

  /**
   * List of images in this column.
   */
  final List<XSImage> images = new ArrayList<XSImage>();

  /**
   * Coordinates (on page) and size of this column in user units.
   */
  final double x;

  final double y;

  final double width;

  final double height;

  /**
   * Internal column name.
   */
  final String name;

  /**
   * Margin around the column.
   */
  final double margin;

  /**
   * Background color of this column. If it is <i>null</i>, the background color of the page will be used.
   */
  Color backgroundColor;

  final int layerID;

  XSColumn(XSPage page, double x, double y, double width, double height, String name, double margin,
      Color backgroundColor, int layerID) {
    this.page = page;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.name = name;
    this.margin = margin;
    this.backgroundColor = backgroundColor;
    this.layerID = layerID;
  }

  boolean addTextPart(String part, XSFontUsage fontUsage, XSLink link) {
    // First text part of this column?
    if (currentTextLine == null) {
      createNewTextLine(fontUsage);
      fontUsage = page.xsPDF.getCurrentFontUsage();
    }

    // New line?
    if (part.equals("\n")) {
      XSTextLine lastTextLine = page.xsPDF.lastTextLine;
      if (lastTextLine != null) {
        lastTextLine.leftAlignWhenJustification = !page.xsPDF.lastLineRightAligned;
      }
      return createNewTextLine(fontUsage);
    }

    while (true) {
      if (!currentTextLine.addTextPart(part, fontUsage, link)) {
        if (!createNewTextLine(fontUsage)) {
          return false;
        }
        fontUsage = page.xsPDF.getCurrentFontUsage();
      } else {
        return true;
      }
    }
  }

  private boolean createNewTextLine(XSFontUsage fontUsage) {
    while (isEnoughRemainderSpaceInColumnForAnotherTextLine(fontUsage)) {
      // Create new (virtual) text line in the same line...
      if (!createNewTextLineOnSameHeightAsLastLine(fontUsage)) {
        // ...or in the next line.
        createNewTextLineBeneathLastLine(fontUsage);
      } else {
        if (!shortenCurrentTextLineWithImages(fontUsage)) {
          createNewTextLineBeneathLastLine(fontUsage);
        }
      }
      if (!shortenCurrentTextLineWithImages(fontUsage)) {
        continue;
      }
      textLines.add(currentTextLine);
      XSPDF xsPDF = page.xsPDF;
      int pageID = page.pageID;
      int lineID = textLines.size();
      for (XSContentListener listener : xsPDF.contentListeners) {
        listener.newTextLine(xsPDF, pageID, name, lineID);
      }
      xsPDF.lastTextLine = currentTextLine;
      return true;
    }
    return false;
  }

  /**
   * This will change the current text line in the following way: (a) Check all images if they overlap the text line. Split the
   * text line if so. (b) Take the first text line which is long enough to hold some words.
   * @return
   */
  private boolean shortenCurrentTextLineWithImages(XSFontUsage fontUsage) {
    List<XSTextLine> subLines = new ArrayList<XSTextLine>();
    subLines.add(currentTextLine);

    // Shorten text line with image barriers.
    for (XSImage image : images) {
      double imX1 = image.x - image.margin;
      double imY1 = image.y - image.margin;
      double imX2 = imX1 + image.width + image.margin * 2;
      double imY2 = imY1 + image.height + image.margin * 2;
      shortenCurrentTextLineWithGivenCoordinates(fontUsage, subLines, imX1, imY1, imX2, imY2);
    }

    // Shorten text line with other column barriers (previous columns on page).
    for (XSColumn prevColumn : page.columns) {
      if (prevColumn == this) {
        break;
      }

      // Only columns with the same layer ID are real "barriers".
      if (prevColumn.layerID == layerID) {
        double pcXX = prevColumn.x - x;
        double pcYY = prevColumn.y - y;
        double pcX1 = pcXX - prevColumn.margin;
        double pcY1 = pcYY - prevColumn.margin;
        double pcX2 = pcX1 + prevColumn.width + 2 * prevColumn.margin;
        double pcY2 = pcY1 + prevColumn.height + 2 * prevColumn.margin;
        shortenCurrentTextLineWithGivenCoordinates(fontUsage, subLines, pcX1, pcY1, pcX2, pcY2);

        // Shorten text line with image barriers in previous columns.
        for (XSImage image : prevColumn.images) {
          double imX1 = image.x - image.margin + pcXX;
          double imY1 = image.y - image.margin + pcYY;
          double imX2 = imX1 + image.width + image.margin * 2;
          double imY2 = imY1 + image.height + image.margin * 2;
          shortenCurrentTextLineWithGivenCoordinates(fontUsage, subLines, imX1, imY1, imX2, imY2);
        }
      }
    }

    // Get next usable (10 spaces width minimal) text line part.
    for (XSTextLine textline : subLines) {
      if (textline.maxWidth > 0) {
        currentTextLine = textline;
        return true;
      }
    }

    // No place anywhere in this line.
    return false;
  }

  private void shortenCurrentTextLineWithGivenCoordinates(XSFontUsage fontUsage, List<XSTextLine> subLines,
      double imX1, double imY1, double imX2, double imY2) {
    // Non-enhanced for loop - subLines content can change during iterations!
    for (int i = 0; i < subLines.size(); i++) {
      XSTextLine textline1 = subLines.get(i);
      double teX1 = textline1.xStartInColumn;
      double teY2 = textline1.yStartInColumn;
      double teX2 = teX1 + textline1.maxWidth;

      // Use current font to check. This could be wrong, maybe...
      double teY1 = teY2 - textline1.getHeight();

      // Is this image in the same line?
      if (imY1 < teY2 && imY2 > teY1) {
        // Change left side?
        if (imX1 <= teX1) {
          if (teX1 < imX2) {
            double dx = imX2 - teX1;
            teX1 += dx;
            textline1.xStartInColumn += dx;
            textline1.maxWidth -= dx;
          }
        }

        // Change right side?
        if (imX2 >= teX2) {
          if (teX2 > imX1) {
            double dx = teX2 - imX1;
            teX2 -= dx;
            textline1.maxWidth -= dx;
          }
        }

        // Split line at image sides?
        if (teX1 < imX1 && imX2 < teX2) {
          // This new text line is the right part...
          subLines.add(new XSTextLine(textline1.column, imX2, teY2, teX2 - imX2, textline1.maxHeight, fontUsage));

          // ...and the old object will have the left part.
          textline1.maxWidth = imX1 - teX1;
        }
      }
    }
  }

  private boolean createNewTextLineOnSameHeightAsLastLine(XSFontUsage fontUsage) {
    if (currentTextLine == null) {
      return false;
    }

    // Check if there is any place between the last (current) line and the right
    // page margin.
    double xStart = currentTextLine.xStartInColumn + currentTextLine.maxWidth;
    double maxWidth = width - xStart;
    if (maxWidth <= 0) {
      return false;
    }

    double yStart = currentTextLine.yStartInColumn;
    double maxHeight = currentTextLine.maxHeight;

    currentTextLine = new XSTextLine(this, xStart, yStart, maxWidth, maxHeight, fontUsage);

    return true;
  }

  private void createNewTextLineBeneathLastLine(XSFontUsage fontUsage) {
    double xStart = 0;
    double yStart = getNewTextLineYStart();
    double maxWidth = width;
    double maxHeight = yStart;
    currentTextLine = new XSTextLine(this, xStart, yStart, maxWidth, maxHeight, fontUsage);
  }

  private boolean isEnoughRemainderSpaceInColumnForAnotherTextLine(XSFontUsage fontUsage) {
    checkColumnHeightAgainstFontHeight(fontUsage);

    // Fits as first line.
    if (currentTextLine == null) {
      return true;
    }

    // Examples remaining page height.
    // double pageMaxY = pageSize.heightInUserUnits - pageMargin.bottom;
    double yStart = getNewTextLineYStart();
    if (yStart >= fontUsage.fontSize) {
      return true;
    }

    // Text will fit on next column (maybe).
    return false;
  }

  /**
   * Check if there is enough place for already one text line on this page.
   */
  private void checkColumnHeightAgainstFontHeight(XSFontUsage fontUsage) {
    if (height < fontUsage.fontSize) {
      throw new XSPdfException("Page not high enough for font size " + fontUsage.fontSize + "!");
    }
  }

  private double getNewTextLineYStart() {
    // First line in column?
    if (currentTextLine == null) {
      return height;
    }

    // Following line on page.
    double currentHeight = currentTextLine.getHeight();
    double leading = getCurrentLineLeadingInUserUnits();
    return currentTextLine.yStartInColumn - currentHeight - leading;
  }

  private double getCurrentLineLeadingInUserUnits() {
    XSLineLeading lineLeading = page.xsPDF.currentLineLeading;
    return lineLeading.value;
  }

  void setImage(String imageID, double imageX, double imageY, double imageWidth, double imageHeight, int pixelWidth,
      int pixelHeight, double margin, XSLink link) {
    if (hasTextContent()) {
      throw new XSPdfException("Please set image on empty page only (empty means \"without any text\")!");
    }
    // imageY is upper side within this column - change it to lower side within
    // this column.
    imageY = height - imageY - imageHeight;
    images.add(new XSImage(imageID, imageX, imageY, imageWidth, imageHeight, pixelWidth, pixelHeight, margin, link));
  }

  boolean hasTextContent() {
    return !textLines.isEmpty();
  }

  private boolean hasImageContent() {
    return !images.isEmpty();
  }

  boolean hasAnyContent() {
    return hasTextContent() || hasImageContent();
  }

  void setColumnEnd() {
    if (currentTextLine != null) {
      currentTextLine.leftAlignWhenJustification = !page.xsPDF.lastLineRightAligned;
    }
  }

  void setBackgroundColor(Color color) {
    backgroundColor = color;
  }
}
